package io.e2x.testground.fruitstore.service.impl;

import io.e2x.testground.fruitstore.common.CouponUtils;
import io.e2x.testground.fruitstore.dal.entities.*;
import io.e2x.testground.fruitstore.data.BillDTO;
import io.e2x.testground.fruitstore.data.BillItemDTO;
import io.e2x.testground.fruitstore.data.Coupon;
import io.e2x.testground.fruitstore.repo.SkuRepo;
import io.e2x.testground.fruitstore.repo.TradeOrderItemRepo;
import io.e2x.testground.fruitstore.repo.TradeOrderRepo;
import io.e2x.testground.fruitstore.service.ProductService;
import io.e2x.testground.fruitstore.service.StoreService;
import io.e2x.testground.fruitstore.service.TradeService;
import io.e2x.testground.fruitstore.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;

@Slf4j
@Component
public class TradeServiceImpl implements TradeService {
    @Autowired
    StoreService storeService;
    @Autowired
    ProductService productService;
    @Autowired
    UserService userService;
    @Autowired
    SkuRepo skuRepo;
    @Autowired
    TradeOrderRepo tradeOrderRepo;
    @Autowired
    TradeOrderItemRepo tradeOrderItemRepo;

    @Override
    public Mono<BillDTO> trade(Store store, User user, List<StoreSpu> storeSpus, Coupon coupon) {
        TradeOrder tradeOrder = new TradeOrder();
        tradeOrder.setUserId(user.getId());
        tradeOrder.setStoreId(store.getId());
        tradeOrder.setNo("N" + System.currentTimeMillis());

        return BillDTO.build(store, user)
                .flatMap(billDTO ->
                        getSumOfPrice(storeSpus).flatMap(price -> {
                            // 总价格计算
                            billDTO.setPrice(price);
                            tradeOrder.setPrice(price);
                            return Mono.just(billDTO);
                        }).then(getSumOfSpuDiscountPrice(storeSpus)
                                .map(sum -> {
                                    // 折扣价格计算
                                    billDTO.setDiscountPrice(billDTO.getPrice().subtract(sum));
                                    return billDTO;
                                }).then(Mono.defer(() ->
                                        Flux.fromIterable(storeSpus)
                                                .flatMap(storeSpu -> {
                                                    return addBillItem(billDTO, storeSpu);
                                                })
                                                .collectList()
                                                .thenReturn(billDTO)
                                )))
                )
                .flatMap(billDTO ->{
                    // 优惠价格
                    if(coupon != null) {
                        BigDecimal couponPrice = CouponUtils.withCoupon(coupon, billDTO.getPrice().subtract(billDTO.getDiscountPrice()));
                        billDTO.setPayPrice(couponPrice);
                        billDTO.setCouponPrice(coupon.getValue());
                        billDTO.setCouponName(CouponUtils.getCouponName(coupon));
                    } else {
                        billDTO.setPayPrice(billDTO.getPrice().subtract(billDTO.getDiscountPrice()));
                        billDTO.setCouponPrice(BigDecimal.valueOf(0));
                        billDTO.setCouponName("无");
                    }
                    // 保存订单
                    tradeOrder.setPrice(billDTO.getPrice());
                    tradeOrder.setPayPrice(billDTO.getPayPrice());
                    tradeOrder.setDiscountPrice(billDTO.getDiscountPrice());
                    tradeOrder.setCouponPrice(billDTO.getCouponPrice());
                    tradeOrder.setCouponType(coupon != null ? CouponUtils.getCouponType(coupon) : null);
                    tradeOrder.setCouponValue(coupon != null ? coupon.getValue() : null);
                    tradeOrder.setCouponLimit(coupon != null ? coupon.getLimit() : null);
                    // 添加订单项
                    return Flux.fromIterable(billDTO.getItems())
                            .flatMap(billItemDTO ->
                                    productService.findBySpuNo(billItemDTO.getSpuNo())
                                            .flatMap(spu ->skuRepo.findById(spu.getSkuId()))
                                            .flatMap(sku ->{
                                                TradeOrderItem tradeOrderItem = new TradeOrderItem();
                                                tradeOrderItem.setOrderId(tradeOrder.getId());
                                                tradeOrderItem.setUserId(user.getId());
                                                tradeOrderItem.setSkuId(sku.getId());
                                                tradeOrderItem.setCount(billItemDTO.getCount());
                                                tradeOrderItem.setPrice(billItemDTO.getPrice());
                                                tradeOrderItem.setPayPrice(billItemDTO.getPayPrice());
                                                billItemDTO.setSkuName(sku.getName());
                                                // 扣减库存， 添加销量
                                                return skuRepo.updateStock(sku.getId(), billItemDTO.getCount())
                                                        .then(tradeOrderItemRepo.save(tradeOrderItem))
                                                        .thenReturn(tradeOrderItem);
                                            }))
                            .collectList()
                            .flatMap(list -> {
                                tradeOrder.setItems(list);
                                return tradeOrderRepo.save(tradeOrder);
                            })
                            .thenReturn(billDTO).flatMap(bill ->
                                            Flux.fromIterable(bill.getItems())
                                                    .flatMap(billItemDTO -> Mono.just(billItemDTO.getPrice())) // 每个 price
                                                    .reduce(BigDecimal::add) // 聚合总价
                                                    .thenReturn(bill)
                                    );
                })
                .doOnNext(bill -> {log.info("{}的账单: {}",bill.getUserName(), bill);});

    }

    private Mono<BigDecimal> getSumOfPrice(List<StoreSpu> storeSpus) {
        return Flux.fromIterable(storeSpus)
                .flatMap(storeSpu ->
                        skuRepo.findById(storeSpu.getSkuId())
                                .flatMap(sku -> Mono.just(sku.getPrice().multiply(BigDecimal.valueOf(storeSpu.getCount()))))
                )
                .reduce(BigDecimal::add);
    }
    private Mono<BigDecimal> getSumOfSpuDiscountPrice(List<StoreSpu> storeSpus) {
        return Flux.fromIterable(storeSpus)
                .flatMap(storeSpu ->
                        skuRepo.findById(storeSpu.getSkuId())
                                .flatMap(sku -> Mono.just(calculatePrice(storeSpu, sku)))
                )
                .reduce(BigDecimal::add);
    }
    private BigDecimal calculatePrice(StoreSpu storeSpu, Sku sku) {
        return (storeSpu.getDiscount().multiply(sku.getPrice()).multiply(BigDecimal.valueOf(storeSpu.getCount())).setScale(2, RoundingMode.HALF_UP));
    }
    private Mono<BillDTO> addBillItem(BillDTO billDTO, StoreSpu storeSpu) {
        return skuRepo.findById(storeSpu.getSkuId())
                .map(sku -> {
                    BillItemDTO billItemDTO = new BillItemDTO();
                    billItemDTO.setSpuNo(storeSpu.getSpuNo());
                    billItemDTO.setSkuName(sku.getName());
                    billItemDTO.setPrice(sku.getPrice());
                    billItemDTO.setPayPrice(calculatePrice(storeSpu, sku));
                    billItemDTO.setCount(storeSpu.getCount());
                    billDTO.getItems().add(billItemDTO);
                    return billDTO;
                })
                .defaultIfEmpty(billDTO);
    }

}
